Skip to content

Chore/better lint rules v3#122

Merged
henri318 merged 10 commits into
mainfrom
chore/better_lint_rules_v3
Jul 7, 2026
Merged

Chore/better lint rules v3#122
henri318 merged 10 commits into
mainfrom
chore/better_lint_rules_v3

Conversation

@henri318

@henri318 henri318 commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Chore/better lint rules v3

Summary by CodeRabbit

  • Bug Fixes
    • Mejorada la gestión de errores en formularios de autenticación, perfil, productos y vendedores.
    • Ajustados varios flujos de paginación, búsqueda y visualización para mostrar datos correctos, incluyendo tamaños, descuentos y mensajes vacíos.
    • Corregida la carga, vista previa y subida de imágenes/personalizaciones para evitar fallos al desmontar componentes o procesar archivos.
    • Afinado el comportamiento del carrito y el checkout para reflejar mejor cambios de precio, cantidades y estados.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@henri318, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 27 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 327d0b01-a8fb-417d-a5e8-4974ed6d20a5

📥 Commits

Reviewing files that changed from the base of the PR and between 25b5a7f and a99a797.

📒 Files selected for processing (3)
  • modules/cart/presentation/components/checkout-confirm-button.tsx
  • modules/uploads/application/create-upload-use-case.ts
  • tests/unit/modules/uploads/application/create-upload-use-case.test.ts
📝 Walkthrough

Walkthrough

Este PR realiza un refactor extenso y transversal de estilo y saneamiento sin cambios funcionales sustanciales en la mayoría de archivos: reemplaza comprobaciones truthy por Object.hasOwn/comparaciones explícitas, migra window/global a globalThis, usa split limitado y replaceAll, reordena miembros de value objects, refactoriza casos de uso de carrito/personalizaciones y reescribe los contenedores de inyección de dependencias.

Changes

Refactor transversal de saneamiento y consistencia

Layer / File(s) Summary
Formularios: manejo de errores y validación
app/[locale]/admin/sellers/create/page.tsx, app/[locale]/auth/*, app/[locale]/checkout/page.tsx, app/[locale]/orders/[orderId]/page.tsx, app/[locale]/products/[id]/*, app/[locale]/profile/page.tsx, app/[locale]/seller/products/product-form.tsx, modules/products/presentation/components/product-actions.tsx, shared/layout/login-modal.tsx
Comprobaciones truthy sobre errores/campos se reemplazan por Object.hasOwn o comparaciones explícitas == null/!= null, y se renombran variables capturadas en bloques catch.
Paginación y sustitución de plantillas
app/[locale]/admin/sellers/page.tsx, app/[locale]/orders/page.tsx, app/[locale]/seller/orders/page.tsx, app/[locale]/seller/products/page.tsx, components/products/infinite-product-list.tsx, components/products/search-input-with-suggestions.tsx
Construcción de URLs de paginación y sustitución de placeholders con split/join en vez de replace encadenado.
Migración window/global a globalThis
modules/cart/presentation/components/*, shared/presentation/lib/to-absolute-url.ts, shared/infrastructure/prisma.ts, shared/layout/*
Múltiples componentes cambian referencias directas a window/global por globalThis.
Parseo de IP/locale con split limitado
app/api/auth/*, app/api/uploads/*, shared/infrastructure/auth-options.ts, proxy.ts, scripts/optimize-sprites.mjs
Extracción de x-forwarded-for y locale usando split con límite explícito.
replaceAll y escapado Unicode
app/api/uploads/local/*, shared/kernel/escape-html.ts, shared/lib/normalize-text.ts, shared/ui/*
Reemplazos de replace/regex global por replaceAll y ajustes de literales Unicode.
Callbacks explícitos en map()
app/api/customizations/route.ts, app/api/sellers/route.ts, modules/*/infrastructure/prisma-*-repository.ts
Map() con función referenciada directamente se reemplaza por callback flecha explícito.
Casos de uso de carrito
modules/cart/application/*
Renombrado de parámetros booleanos, orden determinista de customizationIdList y refactor de migrate-guest-cart en métodos privados.
Componentes de presentación del carrito
modules/cart/presentation/components/*
Refactor de fetch/matching de personalizaciones y extracción de helpers.
Validación de personalizaciones
modules/customizations/*
Validaciones separadas en métodos privados y cambio de requisito text_photo.
Reescritura de containers de DI
composition-root/container.ts, shared/kernel/container.ts
Uso de un objeto state con inicialización perezosa.
Reordenación de value objects
modules/*/domain/value-objects/*, shared/kernel/domain/*
Reordenación de static create y constructor privado; Money/LocalizedDate cambian excepciones a TypeError.
Re-exportaciones de tipos
modules/*/domain/*, shared/*
Cambio de sintaxis/ubicación de re-exports de tipos públicos.
Reubicación de static subscribe
modules/orders/application/*, modules/search-history/application/*
Métodos subscribe reubicados sin cambio funcional.
Otros flujos
app/[locale]/layout.tsx, app/[locale]/page.tsx, modules/uploads/application/*, prisma/seed.ts, eslint.config.mjs
Ajustes puntuales de banner, búsquedas recientes, validación de uploads y configuración de linting.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • henri318/728-store#73: Ambos tocan modules/cart/domain/cart-repository.ts en las re-exportaciones de tipos CartEntity/CartItemEntity.
  • henri318/728-store#104: Los cambios de hiddenFields/SearchForm en app/[locale]/admin/sellers/page.tsx se relacionan con el componente SearchForm compartido introducido en ese PR.
  • henri318/728-store#109: Ambos PRs modifican la lógica de búsquedas recientes en app/[locale]/page.tsx usando GetRecentSearchesUseCase.
🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive El título es genérico y no resume bien el amplio refactor y los ajustes de código del PR, aunque sí alude a cambios de lint. Usa un título más descriptivo, por ejemplo: "Refactor del composition root y limpieza de reglas de lint".
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/better_lint_rules_v3

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
modules/cart/presentation/components/cart-view.tsx (1)

95-100: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Inconsistencia entre la lógica del query param y la del renderizado para size vacío.

En buildCustomizationHref (líneas 99-100) se excluye explícitamente el tamaño cuando es una cadena vacía (customization.size.length > 0), pero en el renderizado del item (línea 320) solo se comprueba != null, por lo que un size === '' sí se mostrará en pantalla como {labels.customizationSize}: con valor vacío, mientras que no se incluirá en la URL de edición. Sería más consistente aplicar la misma comprobación de longitud en ambos sitios.

🐛 Fix propuesto
-                {item.customization.size != null && (
+                {item.customization.size != null &&
+                  item.customization.size.length > 0 && (
                   <span className={styles.customizationLine}>
                     {labels.customizationSize}: {item.customization.size}
                   </span>
                 )}

Also applies to: 320-324

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/cart/presentation/components/cart-view.tsx` around lines 95 - 100,
The logic for customization size is inconsistent between buildCustomizationHref
and the cart item render path in cart-view.tsx: empty size strings are filtered
out for the edit URL but still rendered as an empty value. Update the render
check for the size display to match the URL-building condition by using the same
non-empty length validation as buildCustomizationHref, so size is only shown
when it has content.
modules/email/infrastructure/prisma-email-queue-repository.ts (1)

102-108: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Typo en el comentario JSDoc: "atchSize" debería ser "batchSize".

✏️ Fix propuesto
-   * Atomically claim up to atchSize entries that are due for processing.
+   * Atomically claim up to batchSize entries that are due for processing.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/email/infrastructure/prisma-email-queue-repository.ts` around lines
102 - 108, The JSDoc comment on prisma-email-queue-repository.ts contains a typo
in the parameter name, where “atchSize” should be “batchSize”. Update the
documentation in the claim method area so the comment matches the intended
identifier consistently and remains accurate for readers.
🧹 Nitpick comments (6)
modules/cart/presentation/components/add-to-cart-button.tsx (1)

86-91: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Lógica de "¿tiene contenido de personalización?" duplicada en 3 lugares.

La misma fórmula (hasText(text) || hasText(color) || hasText(size) || hasText(imageUrl) || Boolean(designPosition)) se repite en isAuthCustomizationMatching, en el cálculo de isCustomizationHasContent a nivel de componente, y dentro de performAuthenticatedAdd. Extraer un helper único reduciría el riesgo de que las tres copias diverjan si cambia el criterio.

♻️ Refactor propuesto: helper compartido
 const hasText = (value?: string | null) => value != null && value.length > 0;
+
+function hasCustomizationContent(
+  draft: {
+    text?: string | null;
+    color?: string | null;
+    size?: string | null;
+    imageUrl?: string | null;
+    designPosition?: unknown;
+  } | null,
+): boolean {
+  if (!draft) return false;
+  return (
+    hasText(draft.text) ||
+    hasText(draft.color) ||
+    hasText(draft.size) ||
+    hasText(draft.imageUrl) ||
+    Boolean(draft.designPosition)
+  );
+}

Y reutilizarlo en los tres puntos (isAuthCustomizationMatching, isCustomizationHasContent, performAuthenticatedAdd).

Also applies to: 152-157, 354-360

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/cart/presentation/components/add-to-cart-button.tsx` around lines 86
- 91, The “has customization content” check is duplicated in
add-to-cart-button.tsx across isAuthCustomizationMatching, the
isCustomizationHasContent component calculation, and performAuthenticatedAdd.
Extract that shared logic into a single helper in this component/module (using
the existing hasText, norm.text, norm.color, norm.size, norm.imageUrl, and
norm.designPosition fields) and reuse it in all three places so the criterion
stays consistent.
shared/presentation/lib/to-absolute-url.ts (1)

6-10: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Inconsistencia menor entre window y globalThis.

Se comprueba typeof window === 'undefined' pero luego se usa globalThis.location.origin. Funcionalmente equivalente en el navegador, pero sería más consistente usar globalThis en ambos puntos (o window en ambos).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@shared/presentation/lib/to-absolute-url.ts` around lines 6 - 10, The
`toAbsoluteUrl` helper mixes `window` and `globalThis` for the environment check
and URL resolution, which is inconsistent. Update the logic in `toAbsoluteUrl`
to use the same global reference throughout the function, either by checking
`globalThis` instead of `window` or by using `window` for both the guard and
`location.origin` access.
app/[locale]/products/[id]/customization-form.tsx (1)

60-65: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Inconsistencia de estilo entre campos text y size.

El campo size ahora usa errors.size == null mientras que text sigue usando comprobación truthy (errors.text ? ..., líneas 62, 81 y 86, sin cambios). No es un bug pero sería más consistente aplicar el mismo criterio a ambos campos.

Also applies to: 137-137, 147-147

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/`[locale]/products/[id]/customization-form.tsx around lines 60 - 65, The
error ID checks in customization-form.tsx are inconsistent between the text and
size fields; update the remaining text-related logic in CustomizationForm to use
the same null/undefined check pattern as size. Specifically, align the
conditional used for textErrorId and any related text field error handling with
the errors.size == null approach so the form uses a consistent convention across
both fields.
app/[locale]/auth/verify-email/page.tsx (1)

25-44: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Considerar ignorar AbortError en el catch.

Si token cambiara y el efecto se reejecutara, el catch de la petición abortada anterior también llamaría a setStatus('invalid'), pudiendo pisar el estado correcto de la nueva petición en curso. Es un caso de baja probabilidad (token normalmente fijo), pero fácil de blindar comprobando controller.signal.aborted antes de actualizar el estado.

🛡️ Fix sugerido
       } catch {
-        setStatus('invalid');
+      } catch {
+        if (!controller.signal.aborted) {
+          setStatus('invalid');
+        }
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/`[locale]/auth/verify-email/page.tsx around lines 25 - 44, The
verify-email request handler in the async effect can wrongly set status to
invalid after an aborted fetch when the token changes. Update the try/catch flow
in the verify-email page component so the catch block checks the AbortController
state (for example via controller.signal.aborted) and skips setStatus on aborted
requests, only mapping real failures to invalid. Keep the behavior aligned with
the existing success/expired/invalid logic in the effect that calls fetch and
setStatus.
composition-root/container.ts (1)

105-105: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Tipado débil: Record<string, unknown> para el estado central del contenedor.

Cada getter/setter requiere castear (as X) el valor almacenado en state, perdiendo la verificación en tiempo de compilación que ofrecían las variables individuales tipadas previas. Un typo en la clave (state.emailSedner en vez de state.emailSender, por ejemplo) no sería detectado hasta ejecución.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@composition-root/container.ts` at line 105, El estado central del contenedor
está tipado demasiado débilmente con Record<string, unknown>, lo que obliga a
castings en cada acceso y elimina la verificación de claves y tipos. Refactoriza
el objeto state en composition-root/container.ts para usar un tipo explícito con
propiedades nombradas para cada servicio/configuración, y actualiza los
getters/setters relacionados para leer y escribir con ese tipo en lugar de
depender de as X, usando los símbolos state y los accesores del contenedor para
ubicar los cambios.
shared/kernel/container.ts (1)

24-24: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Tipado débil: state: Record<string, unknown> reemplaza variables tipadas.

El uso de Record<string, unknown> con casts as EmailSender en cada getter/setter renuncia a la seguridad de tipos que ofrecían las variables let tipadas previas. Un error de clave (typo) no sería detectado en tiempo de compilación.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@shared/kernel/container.ts` at line 24, The `state: Record<string, unknown>`
map in `container.ts` weakens type safety and forces `as EmailSender` casts in
the related getters/setters. Replace the untyped bag with explicit typed storage
for each dependency or a strongly typed container interface, and update the
accessor functions to read/write through those typed symbols so typos in keys
are caught at compile time.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@modules/cart/presentation/components/cart-popup.tsx`:
- Around line 115-175: The cart popup fetch flow in cart-popup.tsx has a race
condition because the same AbortController is reused across the initial load and
CART_UPDATED_EVENT refreshes. Update the doFetch logic so each request gets its
own AbortController, and abort any previous in-flight request before starting a
new one; keep the latest response as the only one allowed to call setAuthItems
and setLoading. Use the existing doFetch, ctrl, and handleCartUpdated symbols to
locate and refactor the effect.

In `@modules/customizations/application/create-customer-customization.ts`:
- Around line 63-94: The validation logic in assertModeRequirement is using the
wrong error precedence for mode 'text_photo': the current checks in
CreateCustomerCustomizationApplication can never produce the right message when
both fields are missing, and the final ValidationError in the text_photo branch
is misleading when only text is missing. Update the condition order and/or
branching so the text_photo case is handled explicitly with the correct message
for each missing-field combination, using the assertModeRequirement method and
its hasText/hasImageForCapability checks to distinguish “missing photo”,
“missing text”, and “missing both”.

In `@modules/orders/application/assign-to-production-use-case.ts`:
- Around line 31-35: The JSDoc `@example` blocks in AssignToProductionUseCase have
corrupted Markdown fences, where the opening or closing triple backticks are
split by a tab and break syntax highlighting. Fix the examples in
assign-to-production-use-case.ts by normalizing each fenced code block in the
affected JSDoc comments so the backticks are valid Markdown, and apply the same
correction consistently across all three examples referenced in the class
documentation.

In `@modules/orders/application/mark-as-paid-use-case.ts`:
- Around line 56-63: The PAYMENT_COMPLETED listener in
MarkAsPaidUseCase.subscribe is swallowing exceptions by only logging them, which
makes EventBusPort.emit think processing succeeded. Update the async handler to
rethrow the error after catching it, or route it through the bus’s retry/DLQ
path if available, so failures from useCase.execute(data as MarkAsPaidDTO) are
not hidden and the event contract remains intact.

In `@prisma/seed.ts`:
- Line 608: The error log in the seed failure handler uses a corrupted character
in the message, so update the console.error call in seed.ts to restore the
intended emoji/text instead of the mojibake string. Check the surrounding seed
logging style used in the same script and replace the broken prefix in the catch
block with the correct failure marker so it matches the other emoji-based logs.

---

Outside diff comments:
In `@modules/cart/presentation/components/cart-view.tsx`:
- Around line 95-100: The logic for customization size is inconsistent between
buildCustomizationHref and the cart item render path in cart-view.tsx: empty
size strings are filtered out for the edit URL but still rendered as an empty
value. Update the render check for the size display to match the URL-building
condition by using the same non-empty length validation as
buildCustomizationHref, so size is only shown when it has content.

In `@modules/email/infrastructure/prisma-email-queue-repository.ts`:
- Around line 102-108: The JSDoc comment on prisma-email-queue-repository.ts
contains a typo in the parameter name, where “atchSize” should be “batchSize”.
Update the documentation in the claim method area so the comment matches the
intended identifier consistently and remains accurate for readers.

---

Nitpick comments:
In `@app/`[locale]/auth/verify-email/page.tsx:
- Around line 25-44: The verify-email request handler in the async effect can
wrongly set status to invalid after an aborted fetch when the token changes.
Update the try/catch flow in the verify-email page component so the catch block
checks the AbortController state (for example via controller.signal.aborted) and
skips setStatus on aborted requests, only mapping real failures to invalid. Keep
the behavior aligned with the existing success/expired/invalid logic in the
effect that calls fetch and setStatus.

In `@app/`[locale]/products/[id]/customization-form.tsx:
- Around line 60-65: The error ID checks in customization-form.tsx are
inconsistent between the text and size fields; update the remaining text-related
logic in CustomizationForm to use the same null/undefined check pattern as size.
Specifically, align the conditional used for textErrorId and any related text
field error handling with the errors.size == null approach so the form uses a
consistent convention across both fields.

In `@composition-root/container.ts`:
- Line 105: El estado central del contenedor está tipado demasiado débilmente
con Record<string, unknown>, lo que obliga a castings en cada acceso y elimina
la verificación de claves y tipos. Refactoriza el objeto state en
composition-root/container.ts para usar un tipo explícito con propiedades
nombradas para cada servicio/configuración, y actualiza los getters/setters
relacionados para leer y escribir con ese tipo en lugar de depender de as X,
usando los símbolos state y los accesores del contenedor para ubicar los
cambios.

In `@modules/cart/presentation/components/add-to-cart-button.tsx`:
- Around line 86-91: The “has customization content” check is duplicated in
add-to-cart-button.tsx across isAuthCustomizationMatching, the
isCustomizationHasContent component calculation, and performAuthenticatedAdd.
Extract that shared logic into a single helper in this component/module (using
the existing hasText, norm.text, norm.color, norm.size, norm.imageUrl, and
norm.designPosition fields) and reuse it in all three places so the criterion
stays consistent.

In `@shared/kernel/container.ts`:
- Line 24: The `state: Record<string, unknown>` map in `container.ts` weakens
type safety and forces `as EmailSender` casts in the related getters/setters.
Replace the untyped bag with explicit typed storage for each dependency or a
strongly typed container interface, and update the accessor functions to
read/write through those typed symbols so typos in keys are caught at compile
time.

In `@shared/presentation/lib/to-absolute-url.ts`:
- Around line 6-10: The `toAbsoluteUrl` helper mixes `window` and `globalThis`
for the environment check and URL resolution, which is inconsistent. Update the
logic in `toAbsoluteUrl` to use the same global reference throughout the
function, either by checking `globalThis` instead of `window` or by using
`window` for both the guard and `location.origin` access.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 33ab3ddf-b8d8-4c46-bae2-07e60b1768a6

📥 Commits

Reviewing files that changed from the base of the PR and between 42273b4 and 25b5a7f.

📒 Files selected for processing (124)
  • app/[locale]/admin/sellers/create/page.tsx
  • app/[locale]/admin/sellers/page.tsx
  • app/[locale]/auth/change-password/page.tsx
  • app/[locale]/auth/reset-password/page.tsx
  • app/[locale]/auth/signup/page.tsx
  • app/[locale]/auth/verify-email/page.tsx
  • app/[locale]/cart/design-preview.tsx
  • app/[locale]/checkout/page.tsx
  • app/[locale]/layout.tsx
  • app/[locale]/orders/[orderId]/page.tsx
  • app/[locale]/orders/page.tsx
  • app/[locale]/page.tsx
  • app/[locale]/products/[id]/customization-experience.tsx
  • app/[locale]/products/[id]/customization-form.tsx
  • app/[locale]/products/[id]/customization-preview.tsx
  • app/[locale]/products/[id]/page.tsx
  • app/[locale]/products/[id]/photo-upload-field.tsx
  • app/[locale]/profile/page.tsx
  • app/[locale]/seller/orders/page.tsx
  • app/[locale]/seller/products/page.tsx
  • app/[locale]/seller/products/product-form.tsx
  • app/api/auth/[...nextauth]/route.ts
  • app/api/auth/forgot-password/route.ts
  • app/api/auth/reset-password/route.ts
  • app/api/customizations/route.ts
  • app/api/sellers/route.ts
  • app/api/uploads/guest/presigned-url/route.ts
  • app/api/uploads/local/[...storageKey]/route.ts
  • components/products/infinite-product-list.tsx
  • components/products/search-input-with-suggestions.tsx
  • composition-root/container.ts
  • eslint.config.mjs
  • modules/auth/domain/rate-limiter.ts
  • modules/auth/domain/session.ts
  • modules/auth/domain/user-lookup.ts
  • modules/auth/domain/value-objects/verification-token.ts
  • modules/auth/infrastructure/prisma-rate-limiter.ts
  • modules/cart/application/add-item-to-cart.ts
  • modules/cart/application/checkout-cart.ts
  • modules/cart/application/migrate-guest-cart.ts
  • modules/cart/application/update-cart-item.ts
  • modules/cart/domain/cart-repository.ts
  • modules/cart/domain/value-objects/cart-id.ts
  • modules/cart/domain/value-objects/cart-item-id.ts
  • modules/cart/domain/value-objects/quantity.ts
  • modules/cart/infrastructure/customization-lookup-adapter.ts
  • modules/cart/infrastructure/prisma-cart-repository.ts
  • modules/cart/presentation/components/add-to-cart-button.tsx
  • modules/cart/presentation/components/cart-icon.tsx
  • modules/cart/presentation/components/cart-merge-detector.tsx
  • modules/cart/presentation/components/cart-popup.tsx
  • modules/cart/presentation/components/cart-view.tsx
  • modules/cart/presentation/components/checkout-confirm-button.tsx
  • modules/cart/presentation/components/merge-dialog.tsx
  • modules/cart/presentation/guest-cart-context.tsx
  • modules/customizations/application/create-customer-customization.ts
  • modules/customizations/application/update-customization.ts
  • modules/customizations/domain/value-objects/customization-options.ts
  • modules/customizations/infrastructure/prisma-customization-repository.ts
  • modules/email/infrastructure/prisma-email-queue-repository.ts
  • modules/orders/application/assign-to-production-use-case.ts
  • modules/orders/application/handle-cart-checked-out.ts
  • modules/orders/application/mark-as-paid-use-case.ts
  • modules/orders/domain/order-repository.ts
  • modules/presentation/components/design-preview.tsx
  • modules/products/application/update-product-use-case.ts
  • modules/products/domain/value-objects/category-id.ts
  • modules/products/domain/value-objects/product-customization-config.ts
  • modules/products/domain/value-objects/product-description.ts
  • modules/products/domain/value-objects/product-name.ts
  • modules/products/domain/value-objects/product-price.ts
  • modules/products/infrastructure/prisma-product-repository.ts
  • modules/products/presentation/components/product-actions.tsx
  • modules/roles/infrastructure/prisma-role-repository.ts
  • modules/search-history/application/handle-product-search-executed.ts
  • modules/search-history/domain/search-history-repository.ts
  • modules/sellers/infrastructure/prisma-seller-repository.ts
  • modules/sellers/presentation/components/seller-detail-form.tsx
  • modules/uploads/application/cleanup-uploads-use-case.ts
  • modules/uploads/application/create-upload-use-case.ts
  • modules/uploads/application/delete-upload-use-case.ts
  • modules/uploads/domain/upload-repository.ts
  • modules/uploads/domain/value-objects/upload-status.ts
  • modules/uploads/domain/value-objects/upload-type.ts
  • modules/uploads/infrastructure/r2-storage-adapter.ts
  • modules/users/domain/user-repository.ts
  • prisma/seed.ts
  • proxy.ts
  • scripts/optimize-sprites.mjs
  • shared/contracts/email/email-queue-port.ts
  • shared/i18n/get-dictionary.ts
  • shared/infrastructure/auth-options.ts
  • shared/infrastructure/prisma.ts
  • shared/kernel/container.ts
  • shared/kernel/domain/identifiers/role-id.ts
  • shared/kernel/domain/value-objects/address.ts
  • shared/kernel/domain/value-objects/email.ts
  • shared/kernel/domain/value-objects/localized-date.ts
  • shared/kernel/domain/value-objects/money.ts
  • shared/kernel/domain/value-objects/order-id.ts
  • shared/kernel/domain/value-objects/password-hash.ts
  • shared/kernel/domain/value-objects/payment-id.ts
  • shared/kernel/domain/value-objects/product-id.ts
  • shared/kernel/domain/value-objects/seller-id.ts
  • shared/kernel/domain/value-objects/ticket-id.ts
  • shared/kernel/domain/value-objects/user-id.ts
  • shared/kernel/escape-html.ts
  • shared/layout/header-nav.tsx
  • shared/layout/language-selector.tsx
  • shared/layout/login-modal.tsx
  • shared/lib/normalize-text.ts
  • shared/presentation/components/file-upload-dropzone.tsx
  • shared/presentation/lib/to-absolute-url.ts
  • shared/presentation/status-labels.ts
  • shared/ui/auth-card.tsx
  • shared/ui/eye-toggle-wrapper.tsx
  • shared/ui/file-upload-dropzone.tsx
  • shared/ui/modal.tsx
  • shared/ui/password-strength-indicator.tsx
  • shared/ui/price-field.tsx
  • shared/ui/quantity-controls.tsx
  • shared/ui/tag-list.tsx
  • shared/ui/wave-transition.tsx
  • tests/doubles/memory-rate-limiter.ts

Comment on lines +115 to +175
useEffect(() => {
if (!isOpen || !isAuthenticated) return;
abortRef.current?.abort();
const ctrl = new AbortController();
abortRef.current = ctrl;
setLoading(true);
fetch('/api/cart', { signal: ctrl.signal })
.then((r) => (r.ok ? r.json() : { items: [] }))
.then((data) => {
if (!ctrl.signal.aborted) {
setAuthItems(
(data.items ?? []).map((i: Record<string, unknown>) => {
const customizations =
(i.customizations as Array<Record<string, unknown>>) ?? [];
const firstC = customizations[0] ?? null;
return {
id: i.id as string,
productId: i.productId as string,
productName: i.productName as string,
productImageUrl:
(i.colorImageUrl as string | null) ??
(i.productImageUrl as string | null) ??
null,
sellerId: i.sellerId as string,
sellerName: i.sellerName as string,
quantity: i.quantity as number,
unitPrice: i.unitPrice as number,
lineTotal: +(
(i.unitPrice as number) * (i.quantity as number)
).toFixed(2),
customization: firstC
? {
text: (firstC.text as string | null) ?? null,
color: (firstC.color as string | null) ?? null,
size: (firstC.size as string | null) ?? null,
imageUrl: (firstC.imageUrl as string | null) ?? null,
designPosition:
(firstC.designPosition as Record<string, unknown>) ??
null,
}
: null,
} as CartItemDTO;
}),
);
const doFetch = async () => {
setLoading(true);
try {
const res = await fetch('/api/cart', { signal: ctrl.signal });
if (ctrl.signal.aborted) return;
if (!res.ok) {
setLoading(false);
return;
}
})
.catch(() => {
const data = await res.json();
if (ctrl.signal.aborted) return;
setAuthItems(
(data.items ?? []).map((i: Record<string, unknown>) => {
const customizations =
(i.customizations as Array<Record<string, unknown>>) ?? [];
const firstC = customizations[0] ?? null;
return {
id: i.id as string,
productId: i.productId as string,
productName: i.productName as string,
productImageUrl:
(i.colorImageUrl as string | null) ??
(i.productImageUrl as string | null) ??
null,
sellerId: i.sellerId as string,
sellerName: i.sellerName as string,
quantity: i.quantity as number,
unitPrice: i.unitPrice as number,
lineTotal: +(
(i.unitPrice as number) * (i.quantity as number)
).toFixed(2),
customization: firstC
? {
text: (firstC.text as string | null) ?? null,
color: (firstC.color as string | null) ?? null,
size: (firstC.size as string | null) ?? null,
imageUrl: (firstC.imageUrl as string | null) ?? null,
designPosition:
(firstC.designPosition as Record<string, unknown>) ??
null,
}
: null,
} as CartItemDTO;
}),
);
setLoading(false);
} catch {
if (!ctrl.signal.aborted) setLoading(false);
});
}, [isOpen, isAuthenticated]);

useEffect(() => {
if (!isOpen || !isAuthenticated) return;
refreshAuthCart();
const handleCartUpdated = () => refreshAuthCart();
window.addEventListener(CART_UPDATED_EVENT, handleCartUpdated);
}
};
doFetch();
const handleCartUpdated = () => doFetch();
globalThis.addEventListener(CART_UPDATED_EVENT, handleCartUpdated);
return () => {
abortRef.current?.abort();
window.removeEventListener(CART_UPDATED_EVENT, handleCartUpdated);
ctrl.abort();
globalThis.removeEventListener(CART_UPDATED_EVENT, handleCartUpdated);
};
}, [isOpen, isAuthenticated, refreshAuthCart]);
}, [isOpen, isAuthenticated]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Condición de carrera: peticiones doFetch concurrentes no se cancelan entre sí.

El AbortController (ctrl) se crea una sola vez por ejecución del efecto y se reutiliza tanto en la carga inicial como en cada disparo de CART_UPDATED_EVENT. Si el evento se dispara mientras una petición anterior sigue en vuelo (p.ej. clics rápidos en los controles de cantidad, cada uno llamando a dispatchCartUpdated() al completarse), la nueva llamada a doFetch() no cancela la anterior. Las respuestas pueden resolver fuera de orden y la que complete al final (no necesariamente la más reciente) sobrescribe authItems, mostrando temporalmente datos desactualizados del carrito.

🔧 Fix sugerido: crear un nuevo `AbortController` por cada `doFetch`, abortando el anterior
   useEffect(() => {
     if (!isOpen || !isAuthenticated) return;
-    const ctrl = new AbortController();
+    let ctrl = new AbortController();
     const doFetch = async () => {
+      ctrl.abort();
+      ctrl = new AbortController();
+      const localCtrl = ctrl;
       setLoading(true);
       try {
-        const res = await fetch('/api/cart', { signal: ctrl.signal });
-        if (ctrl.signal.aborted) return;
+        const res = await fetch('/api/cart', { signal: localCtrl.signal });
+        if (localCtrl.signal.aborted) return;
         if (!res.ok) {
           setLoading(false);
           return;
         }
         const data = await res.json();
-        if (ctrl.signal.aborted) return;
+        if (localCtrl.signal.aborted) return;
         ...
-        setLoading(false);
+        setLoading(false);
       } catch {
-        if (!ctrl.signal.aborted) setLoading(false);
+        if (!localCtrl.signal.aborted) setLoading(false);
       }
     };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
if (!isOpen || !isAuthenticated) return;
abortRef.current?.abort();
const ctrl = new AbortController();
abortRef.current = ctrl;
setLoading(true);
fetch('/api/cart', { signal: ctrl.signal })
.then((r) => (r.ok ? r.json() : { items: [] }))
.then((data) => {
if (!ctrl.signal.aborted) {
setAuthItems(
(data.items ?? []).map((i: Record<string, unknown>) => {
const customizations =
(i.customizations as Array<Record<string, unknown>>) ?? [];
const firstC = customizations[0] ?? null;
return {
id: i.id as string,
productId: i.productId as string,
productName: i.productName as string,
productImageUrl:
(i.colorImageUrl as string | null) ??
(i.productImageUrl as string | null) ??
null,
sellerId: i.sellerId as string,
sellerName: i.sellerName as string,
quantity: i.quantity as number,
unitPrice: i.unitPrice as number,
lineTotal: +(
(i.unitPrice as number) * (i.quantity as number)
).toFixed(2),
customization: firstC
? {
text: (firstC.text as string | null) ?? null,
color: (firstC.color as string | null) ?? null,
size: (firstC.size as string | null) ?? null,
imageUrl: (firstC.imageUrl as string | null) ?? null,
designPosition:
(firstC.designPosition as Record<string, unknown>) ??
null,
}
: null,
} as CartItemDTO;
}),
);
const doFetch = async () => {
setLoading(true);
try {
const res = await fetch('/api/cart', { signal: ctrl.signal });
if (ctrl.signal.aborted) return;
if (!res.ok) {
setLoading(false);
return;
}
})
.catch(() => {
const data = await res.json();
if (ctrl.signal.aborted) return;
setAuthItems(
(data.items ?? []).map((i: Record<string, unknown>) => {
const customizations =
(i.customizations as Array<Record<string, unknown>>) ?? [];
const firstC = customizations[0] ?? null;
return {
id: i.id as string,
productId: i.productId as string,
productName: i.productName as string,
productImageUrl:
(i.colorImageUrl as string | null) ??
(i.productImageUrl as string | null) ??
null,
sellerId: i.sellerId as string,
sellerName: i.sellerName as string,
quantity: i.quantity as number,
unitPrice: i.unitPrice as number,
lineTotal: +(
(i.unitPrice as number) * (i.quantity as number)
).toFixed(2),
customization: firstC
? {
text: (firstC.text as string | null) ?? null,
color: (firstC.color as string | null) ?? null,
size: (firstC.size as string | null) ?? null,
imageUrl: (firstC.imageUrl as string | null) ?? null,
designPosition:
(firstC.designPosition as Record<string, unknown>) ??
null,
}
: null,
} as CartItemDTO;
}),
);
setLoading(false);
} catch {
if (!ctrl.signal.aborted) setLoading(false);
});
}, [isOpen, isAuthenticated]);
useEffect(() => {
if (!isOpen || !isAuthenticated) return;
refreshAuthCart();
const handleCartUpdated = () => refreshAuthCart();
window.addEventListener(CART_UPDATED_EVENT, handleCartUpdated);
}
};
doFetch();
const handleCartUpdated = () => doFetch();
globalThis.addEventListener(CART_UPDATED_EVENT, handleCartUpdated);
return () => {
abortRef.current?.abort();
window.removeEventListener(CART_UPDATED_EVENT, handleCartUpdated);
ctrl.abort();
globalThis.removeEventListener(CART_UPDATED_EVENT, handleCartUpdated);
};
}, [isOpen, isAuthenticated, refreshAuthCart]);
}, [isOpen, isAuthenticated]);
useEffect(() => {
if (!isOpen || !isAuthenticated) return;
let ctrl = new AbortController();
const doFetch = async () => {
ctrl.abort();
ctrl = new AbortController();
const localCtrl = ctrl;
setLoading(true);
try {
const res = await fetch('/api/cart', { signal: localCtrl.signal });
if (localCtrl.signal.aborted) return;
if (!res.ok) {
setLoading(false);
return;
}
const data = await res.json();
if (localCtrl.signal.aborted) return;
setAuthItems(
(data.items ?? []).map((i: Record<string, unknown>) => {
const customizations =
(i.customizations as Array<Record<string, unknown>>) ?? [];
const firstC = customizations[0] ?? null;
return {
id: i.id as string,
productId: i.productId as string,
productName: i.productName as string,
productImageUrl:
(i.colorImageUrl as string | null) ??
(i.productImageUrl as string | null) ??
null,
sellerId: i.sellerId as string,
sellerName: i.sellerName as string,
quantity: i.quantity as number,
unitPrice: i.unitPrice as number,
lineTotal: +(
(i.unitPrice as number) * (i.quantity as number)
).toFixed(2),
customization: firstC
? {
text: (firstC.text as string | null) ?? null,
color: (firstC.color as string | null) ?? null,
size: (firstC.size as string | null) ?? null,
imageUrl: (firstC.imageUrl as string | null) ?? null,
designPosition:
(firstC.designPosition as Record<string, unknown>) ??
null,
}
: null,
} as CartItemDTO;
}),
);
setLoading(false);
} catch {
if (!localCtrl.signal.aborted) setLoading(false);
}
};
doFetch();
const handleCartUpdated = () => doFetch();
globalThis.addEventListener(CART_UPDATED_EVENT, handleCartUpdated);
return () => {
ctrl.abort();
globalThis.removeEventListener(CART_UPDATED_EVENT, handleCartUpdated);
};
}, [isOpen, isAuthenticated]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/cart/presentation/components/cart-popup.tsx` around lines 115 - 175,
The cart popup fetch flow in cart-popup.tsx has a race condition because the
same AbortController is reused across the initial load and CART_UPDATED_EVENT
refreshes. Update the doFetch logic so each request gets its own
AbortController, and abort any previous in-flight request before starting a new
one; keep the latest response as the only one allowed to call setAuthItems and
setLoading. Use the existing doFetch, ctrl, and handleCartUpdated symbols to
locate and refactor the effect.

Comment on lines +63 to +94
private assertModeRequirement(
dto: CreateCustomerCustomizationDTO,
config: ProductCustomizationConfig,
): void {
const hasText = dto.text !== undefined && dto.text !== null;
const hasImage = dto.imageUrl !== undefined && dto.imageUrl !== null;
const hasDesignPosition =
dto.designPosition !== undefined && dto.designPosition !== null;
const hasImageForCapability = hasImage || hasDesignPosition;
const mode = config.mode;

switch (config.mode) {
case 'description':
case 'text':
if (!hasText) {
throw new ValidationError(
'Text customization is required for this product',
'Customization is not allowed for this product',
);
}
break;
case 'photo':
if (!hasImageForCapability) {
throw new ValidationError(
'Photo customization is required for this product',
'Customization is not allowed for this product',
);
}
break;
case 'text_photo':
if (!hasText && !hasImageForCapability) {
throw new ValidationError(
'Text or photo customization is required for this product',
'Customization is not allowed for this product',
);
}
break;
if ((mode === 'description' || mode === 'text') && !hasText) {
throw new ValidationError(
'Text customization is required for this product',
'Customization is not allowed for this product',
);
}

if ((mode === 'photo' || mode === 'text_photo') && !hasImageForCapability) {
throw new ValidationError(
'Photo customization is required for this product',
'Customization is not allowed for this product',
);
}

if (mode === 'text_photo' && (!hasText || !hasImageForCapability)) {
throw new ValidationError(
'Both text and photo customization are required for this product',
'Customization is not allowed for this product',
);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Mensajes de error incorrectos para el modo text_photo.

El tercer if (línea 88) es prácticamente inalcanzable en su forma correcta: si falta la foto, el segundo if (línea 81) ya lanza antes con el mensaje "Photo customization is required", por lo que el tercer if solo se ejecuta cuando la foto SÍ está presente y falta el texto — pero en ese caso lanza el mensaje "Both text and photo customization are required", que es engañoso (solo falta el texto). Igualmente, cuando faltan ambos, el mensaje dice solo "Photo customization is required" sin mencionar el texto.

🐛 Fix propuesto: precedencia correcta de mensajes para `text_photo`
-    if ((mode === 'photo' || mode === 'text_photo') && !hasImageForCapability) {
-      throw new ValidationError(
-        'Photo customization is required for this product',
-        'Customization is not allowed for this product',
-      );
-    }
-
-    if (mode === 'text_photo' && (!hasText || !hasImageForCapability)) {
-      throw new ValidationError(
-        'Both text and photo customization are required for this product',
-        'Customization is not allowed for this product',
-      );
-    }
+    if (mode === 'photo' && !hasImageForCapability) {
+      throw new ValidationError(
+        'Photo customization is required for this product',
+        'Customization is not allowed for this product',
+      );
+    }
+
+    if (mode === 'text_photo') {
+      if (!hasText && !hasImageForCapability) {
+        throw new ValidationError(
+          'Both text and photo customization are required for this product',
+          'Customization is not allowed for this product',
+        );
+      }
+      if (!hasText) {
+        throw new ValidationError(
+          'Text customization is required for this product',
+          'Customization is not allowed for this product',
+        );
+      }
+      if (!hasImageForCapability) {
+        throw new ValidationError(
+          'Photo customization is required for this product',
+          'Customization is not allowed for this product',
+        );
+      }
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private assertModeRequirement(
dto: CreateCustomerCustomizationDTO,
config: ProductCustomizationConfig,
): void {
const hasText = dto.text !== undefined && dto.text !== null;
const hasImage = dto.imageUrl !== undefined && dto.imageUrl !== null;
const hasDesignPosition =
dto.designPosition !== undefined && dto.designPosition !== null;
const hasImageForCapability = hasImage || hasDesignPosition;
const mode = config.mode;
switch (config.mode) {
case 'description':
case 'text':
if (!hasText) {
throw new ValidationError(
'Text customization is required for this product',
'Customization is not allowed for this product',
);
}
break;
case 'photo':
if (!hasImageForCapability) {
throw new ValidationError(
'Photo customization is required for this product',
'Customization is not allowed for this product',
);
}
break;
case 'text_photo':
if (!hasText && !hasImageForCapability) {
throw new ValidationError(
'Text or photo customization is required for this product',
'Customization is not allowed for this product',
);
}
break;
if ((mode === 'description' || mode === 'text') && !hasText) {
throw new ValidationError(
'Text customization is required for this product',
'Customization is not allowed for this product',
);
}
if ((mode === 'photo' || mode === 'text_photo') && !hasImageForCapability) {
throw new ValidationError(
'Photo customization is required for this product',
'Customization is not allowed for this product',
);
}
if (mode === 'text_photo' && (!hasText || !hasImageForCapability)) {
throw new ValidationError(
'Both text and photo customization are required for this product',
'Customization is not allowed for this product',
);
}
}
private assertModeRequirement(
dto: CreateCustomerCustomizationDTO,
config: ProductCustomizationConfig,
): void {
const hasText = dto.text !== undefined && dto.text !== null;
const hasImage = dto.imageUrl !== undefined && dto.imageUrl !== null;
const hasDesignPosition =
dto.designPosition !== undefined && dto.designPosition !== null;
const hasImageForCapability = hasImage || hasDesignPosition;
const mode = config.mode;
if ((mode === 'description' || mode === 'text') && !hasText) {
throw new ValidationError(
'Text customization is required for this product',
'Customization is not allowed for this product',
);
}
if (mode === 'photo' && !hasImageForCapability) {
throw new ValidationError(
'Photo customization is required for this product',
'Customization is not allowed for this product',
);
}
if (mode === 'text_photo') {
if (!hasText && !hasImageForCapability) {
throw new ValidationError(
'Both text and photo customization are required for this product',
'Customization is not allowed for this product',
);
}
if (!hasText) {
throw new ValidationError(
'Text customization is required for this product',
'Customization is not allowed for this product',
);
}
if (!hasImageForCapability) {
throw new ValidationError(
'Photo customization is required for this product',
'Customization is not allowed for this product',
);
}
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/customizations/application/create-customer-customization.ts` around
lines 63 - 94, The validation logic in assertModeRequirement is using the wrong
error precedence for mode 'text_photo': the current checks in
CreateCustomerCustomizationApplication can never produce the right message when
both fields are missing, and the final ValidationError in the text_photo branch
is misleading when only text is missing. Update the condition order and/or
branching so the text_photo case is handled explicitly with the correct message
for each missing-field combination, using the assertModeRequirement method and
its hasText/hasImageForCapability checks to distinguish “missing photo”,
“missing text”, and “missing both”.

Comment on lines +31 to 35
* ` ypescript
* const useCase = new AssignToProductionUseCase(orderRepo, outboxRepo, transactionalService);
* await useCase.execute({ orderId: 'order-1', customizationId: 'custom-1' });
* ```
* `
*/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Bloques de código Markdown corruptos en los comentarios JSDoc.

Las vallas de código triples (```typescript) aparecen como un backtick seguido de un tabulador (` ypescript), lo que rompe el resaltado de sintaxis en el ejemplo @example en herramientas que renderizan JSDoc (IDEs, generadores de documentación). Ocurre en tres lugares: líneas 31-34, 50-53 y 101-106.

📝 Fix propuesto
- * `	ypescript
+ * ```typescript
  * const useCase = new AssignToProductionUseCase(orderRepo, outboxRepo, transactionalService);
  * await useCase.execute({ orderId: 'order-1', customizationId: 'custom-1' });
- * `
+ * ```
  */

Aplicar el mismo arreglo a los otros dos bloques de ejemplo (líneas 50-53 y 101-106).

Also applies to: 50-53, 101-106

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/orders/application/assign-to-production-use-case.ts` around lines 31
- 35, The JSDoc `@example` blocks in AssignToProductionUseCase have corrupted
Markdown fences, where the opening or closing triple backticks are split by a
tab and break syntax highlighting. Fix the examples in
assign-to-production-use-case.ts by normalizing each fenced code block in the
affected JSDoc comments so the backticks are valid Markdown, and apply the same
correction consistently across all three examples referenced in the class
documentation.

Comment on lines +56 to +63
static subscribe(eventBus: EventBusPort, useCase: MarkAsPaidUseCase): void {
eventBus.on(GlobalEvents.PAYMENT_COMPLETED, async (data: unknown) => {
try {
await useCase.execute(data as MarkAsPaidDTO);
} catch (error) {
console.error('Error processing PaymentCompleted event:', error);
}
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

No ocultes la excepción del listener.

emit() deja de fallar aunque execute(...) no se haya aplicado, así que PAYMENT_COMPLETED puede darse por procesado cuando en realidad no lo está. Re-lanza el error (o enrútalo a retry/DLQ) para no romper el contrato del bus.

🔧 Posible ajuste
     eventBus.on(GlobalEvents.PAYMENT_COMPLETED, async (data: unknown) => {
       try {
         await useCase.execute(data as MarkAsPaidDTO);
       } catch (error) {
         console.error('Error processing PaymentCompleted event:', error);
+        throw error;
       }
     });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
static subscribe(eventBus: EventBusPort, useCase: MarkAsPaidUseCase): void {
eventBus.on(GlobalEvents.PAYMENT_COMPLETED, async (data: unknown) => {
try {
await useCase.execute(data as MarkAsPaidDTO);
} catch (error) {
console.error('Error processing PaymentCompleted event:', error);
}
});
static subscribe(eventBus: EventBusPort, useCase: MarkAsPaidUseCase): void {
eventBus.on(GlobalEvents.PAYMENT_COMPLETED, async (data: unknown) => {
try {
await useCase.execute(data as MarkAsPaidDTO);
} catch (error) {
console.error('Error processing PaymentCompleted event:', error);
throw error;
}
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/orders/application/mark-as-paid-use-case.ts` around lines 56 - 63,
The PAYMENT_COMPLETED listener in MarkAsPaidUseCase.subscribe is swallowing
exceptions by only logging them, which makes EventBusPort.emit think processing
succeeded. Update the async handler to rethrow the error after catching it, or
route it through the bus’s retry/DLQ path if available, so failures from
useCase.execute(data as MarkAsPaidDTO) are not hidden and the event contract
remains intact.

Comment thread prisma/seed.ts
try {
await main();
} catch (error) {
console.error('? Seed failed:', error);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Carácter corrupto en el mensaje de error.

'? Seed failed:' parece un emoji corrupto (mojibake), ya que el resto del script usa emojis reales (🌱, , ) en sus logs. Verificar la codificación del archivo y restaurar el emoji original (p.ej. ).

✏️ Fix sugerido
-        console.error('? Seed failed:', error);
+        console.error('❌ Seed failed:', error);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.error('? Seed failed:', error);
console.error(' Seed failed:', error);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@prisma/seed.ts` at line 608, The error log in the seed failure handler uses a
corrupted character in the message, so update the console.error call in seed.ts
to restore the intended emoji/text instead of the mojibake string. Check the
surrounding seed logging style used in the same script and replace the broken
prefix in the catch block with the correct failure marker so it matches the
other emoji-based logs.

Developer added 2 commits July 7, 2026 13:16
@henri318 henri318 merged commit cf7d6c9 into main Jul 7, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant